package com.ycxh.module.FileStore.util;


import cn.hutool.extra.qrcode.QrCodeUtil;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
 * 二维码生成工具类
 * https://cli.im/deqr/other 在线解码
 **/
public class QRCodeUtil {

    //编码
    private static final String CHARSET = "utf-8";
    //文件格式
    private static final String FORMAT = "JPG";
    // 二维码尺寸
    private static final int QRCODE_SIZE = 300;
    // LOGO宽度
    private static final int LOGO_WIDTH = 60;
    // LOGO高度
    private static final int LOGO_HEIGHT = 60;

    /**
     * 生成二维码
     *
     * @Param [content, logoPath, needCompress] 内容,logo路径,是否压缩
     **/
    public static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {
        Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
                hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        if (logoPath == null || "".equals(logoPath)) {
            return image;
        }
        // 插入图片
        QRCodeUtil.insertImage(image, logoPath, needCompress);
        return image;
    }

    /**
     * 插入logo
     *
     * @Param [source, logoPath, needCompress] 二维码图片,logo路径,是否压缩
     **/
    private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = QRCodeUtil.getResourceAsStream(logoPath);
            Image src = ImageIO.read(inputStream);
            int width = src.getWidth(null);
            int height = src.getHeight(null);
            if (needCompress) {
                // 压缩LOGO
                width = width > LOGO_WIDTH ? LOGO_WIDTH : width;
                height = height > LOGO_HEIGHT ? LOGO_HEIGHT : height;
                Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
                BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics g = tag.getGraphics();
                g.drawImage(image, 0, 0, null);
                // 绘制缩小后的图
                g.dispose();
                src = image;
            }
            // 插入LOGO
            Graphics2D graph = source.createGraphics();
            int x = (QRCODE_SIZE - width) / 2;
            int y = (QRCODE_SIZE - height) / 2;
            graph.drawImage(src, x, y, width, height, null);
            Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
            graph.setStroke(new BasicStroke(3f));
            graph.draw(shape);
            graph.dispose();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 生成二维码, 获得到输出流 ,logo内嵌
     *
     * @Param [content, logoPath, output, needCompress] 内容,logo路径,输出流,是否压缩
     **/
    public static void encode(String content, String logoPath, OutputStream output, boolean needCompress)
            throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);
        ImageIO.write(image, FORMAT, output);
    }

    /**
     * 获取指定文件的输入流，获取logo
     *
     * @Param [logoPath] logo路径
     **/
    public static InputStream getResourceAsStream(String logoPath) {
        return QRCodeUtil.class.getResourceAsStream(logoPath);
    }

    public static void main(String[] args) throws Exception {
        // Caused by: com.google.zxing.NotFoundException
        // System.out.println(deEncodeByPath("C:\\Users\\74102\\Downloads\\Snipaste_2023-10-27_16-56-32.png"));
        System.out.println(uploadByTrack("C:\\Users\\74102\\Downloads\\Snipaste_2023-10-27_17-05-33.png"));

        String fullPath = "C:\\Users\\74102\\Downloads\\Snipaste_2023-10-27_16-56-32.png";
        String newPath = "C:\\Users\\74102\\Downloads\\Snipaste_2023-10-27_16-56-33.png";
        try {
            binarization(fullPath, newPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析二维码,此方法解析一个路径的二维码图片（带logo）
     * https://blog.csdn.net/XuDream/article/details/124273117
     * path:二维码图片路径
     */
    public static String deEncodeByPath(String path) {
        String content = null;
        BufferedImage image;
        try {
            image = ImageIO.read(new File(path));

            LuminanceSource source = new BufferedImageLuminanceSource(image);
            Binarizer binarizer = new HybridBinarizer(source);
            // Binarizer binarizer = new GlobalHistogramBinarizer(source);
            BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
            Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
            hints.put(DecodeHintType.CHARACTER_SET, "GBK");
            hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
            Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解码
            System.out.println("图片中内容：  ");
            System.out.println("content： " + result.getText());
            content = result.getText();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            //这里判断如果识别不了带LOGO的图片，重新添加上一个属性
            try {
                image = ImageIO.read(new File(path));
                LuminanceSource source = new BufferedImageLuminanceSource(image);
                Binarizer binarizer = new HybridBinarizer(source);
                BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
                Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
                //设置编码格式
                hints.put(DecodeHintType.CHARACTER_SET, "GBK");
                //设置优化精度
                hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
                //设置复杂模式开启（我使用这种方式就可以识别微信的二维码了）
                hints.put(DecodeHintType.PURE_BARCODE, Boolean.TYPE);
                Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解码
                System.out.println("图片中内容：  ");
                System.out.println("content： " + result.getText());
                content = result.getText();
            } catch (IOException e1) {
                e1.printStackTrace();
            } catch (NotFoundException e2) {
                e2.printStackTrace();
            }
        }
        return content;
    }

    /**
     * 解析本地二维码内容(直解析utf-8和GBK编码的二维码)
     *
     * @param filePath 二维码图片地址
     * @throws IOException
     * @throws NotFoundException
     */
    public static String analysis(String filePath) throws IOException, NotFoundException {
        BufferedImage image = ImageIO.read(new File(filePath));
        BinaryBitmap bb = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(image)));
        HashMap map = new HashMap();
        map.put(DecodeHintType.CHARACTER_SET, "utf-8");
        Result result = new MultiFormatReader().decode(bb, map);
        String QRContents = result.getText();
        if (result.getText().contains("�")) {
            //乱码了，更换编码方式
            System.out.println("乱码了，更换编码格式(GBK)");
            map.put(DecodeHintType.CHARACTER_SET, "GBK");
            result = new MultiFormatReader().decode(bb, map);
            QRContents = result.getText();
        }
        System.out.println("二维码文本内容：" + QRContents);
        return QRContents;
    }

    //原文链接：https://blog.csdn.net/susu1083018911/article/details/123870957
    public static String decode(String filePath) throws Exception {
        File file = new File(filePath);
        BufferedImage image;
        image = ImageIO.read(file);
        if (image == null) {
            return null;
        }
        LuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        // BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
        // BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source.invert()));
        // BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source.invert()));
        Result result;
        Hashtable hints = new Hashtable();
        // hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        hints.put(DecodeHintType.CHARACTER_SET, "GBK");
        // hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
        result = new MultiFormatReader().decode(bitmap, hints);
        String resultStr = result.getText();

        return resultStr;
    }


    /**
     * hutool识别图片里的二维码失败，使用灰度处理
     * https://blog.csdn.net/SSD1397713787/article/details/132163328
     *
     * @param filePath
     * @return
     * @throws IOException
     */
    public static String uploadByTrack(String filePath) throws IOException {
        File file = new File(filePath);

        // 直接解析报错：Caused by: com.google.zxing.NotFoundException
        // String decode = QrCodeUtil.decode(file);
        // System.out.println(decode);

        // 经过灰度处理之后，可以识别
        BufferedImage bufferedImage = ImageIO.read(file);
        // 对BufferedImage进行灰度处理
        BufferedImage grayImage = convertToGrayscale(bufferedImage);
        // 将灰度图像保存为文件
        File outputFile = new File(file.getParentFile(), System.currentTimeMillis() + ".PNG");
        // 写入内存
        ImageIO.write(grayImage, "png", outputFile);
        // 解析灰度图片
        String decode = null;
        try {
            decode = QrCodeUtil.decode(outputFile);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("解析出的数据----- + decode");

            // bufferedImage = ImageIO.read(outputFile);
            bufferedImage = ImageIO.read(file);

            //设置优化精度
            // hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
            //设置复杂模式开启（我使用这种方式就可以识别微信的二维码了）
            // hints.put(DecodeHintType.PURE_BARCODE, Boolean.TYPE);

            decode = QrCodeUtil.decode(bufferedImage, Boolean.TRUE, Boolean.TRUE);


            // decode = deEncodeByPath(outputFile.getAbsolutePath());
        } finally {
            if (outputFile.exists()) {
                outputFile.delete();//删除临时灰度图片
            }
        }


        return decode;
    }

    // 灰度处理
    private static BufferedImage convertToGrayscale(BufferedImage originalImage) {
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();

        BufferedImage grayImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int rgb = originalImage.getRGB(x, y);
                Color color = new Color(rgb);
                int gray = (int) (color.getRed() * 0.299 + color.getGreen() * 0.587 + color.getBlue() * 0.114);
                Color grayColor = new Color(gray, gray, gray);
                grayImage.setRGB(x, y, grayColor.getRGB());
            }
        }

        return grayImage;
    }


    // 阈值0-255
    public static int YZ = 200;

    /**
     * 图像二值化处理 依然不行
     * 关于二维码识别报错com.google.zxing.NotFoundException的解决方案
     *
     * @param filePath       要处理的图片路径
     * @param fileOutputPath 处理后的图片输出路径
     */
    public static void binarization(String filePath, String fileOutputPath) throws IOException {
        File file = new File(filePath);
        BufferedImage bi = ImageIO.read(file);
        // 获取当前图片的高,宽,ARGB
        int h = bi.getHeight();
        int w = bi.getWidth();
        int arr[][] = new int[w][h];

        // 获取图片每一像素点的灰度值
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                // getRGB()返回默认的RGB颜色模型(十进制)
                arr[i][j] = getImageGray(bi.getRGB(i, j));// 该点的灰度值
            }
        }

        // 构造一个类型为预定义图像类型,BufferedImage
        BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);

        // 和预先设置的阈值大小进行比较，大的就显示为255即白色，小的就显示为0即黑色
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                if (getGray(arr, i, j, w, h) > YZ) {
                    int white = new Color(255, 255, 255).getRGB();
                    bufferedImage.setRGB(i, j, white);
                } else {
                    int black = new Color(0, 0, 0).getRGB();
                    bufferedImage.setRGB(i, j, black);
                }
            }

        }
        ImageIO.write(bufferedImage, "jpg", new File(fileOutputPath));
    }

    /**
     * 图像的灰度处理
     * 利用浮点算法：Gray = R*0.3 + G*0.59 + B*0.11;
     *
     * @param rgb 该点的RGB值
     * @return 返回处理后的灰度值
     */
    private static int getImageGray(int rgb) {
        String argb = Integer.toHexString(rgb);// 将十进制的颜色值转为十六进制
        // argb分别代表透明,红,绿,蓝 分别占16进制2位
        int r = Integer.parseInt(argb.substring(2, 4), 16);// 后面参数为使用进制
        int g = Integer.parseInt(argb.substring(4, 6), 16);
        int b = Integer.parseInt(argb.substring(6, 8), 16);
        int gray = (int) (r * 0.28 + g * 0.95 + b * 0.11);
        return gray;
    }

    /**
     * 自己加周围8个灰度值再除以9，算出其相对灰度值
     *
     * @param gray
     * @param x    要计算灰度的点的横坐标
     * @param y    要计算灰度的点的纵坐标
     * @param w    图像的宽度
     * @param h    图像的高度
     * @return
     */
    public static int getGray(int gray[][], int x, int y, int w, int h) {
        int rs = gray[x][y] + (x == 0 ? 255 : gray[x - 1][y]) + (x == 0 || y == 0 ? 255 : gray[x - 1][y - 1])
                + (x == 0 || y == h - 1 ? 255 : gray[x - 1][y + 1]) + (y == 0 ? 255 : gray[x][y - 1])
                + (y == h - 1 ? 255 : gray[x][y + 1]) + (x == w - 1 ? 255 : gray[x + 1][y])
                + (x == w - 1 || y == 0 ? 255 : gray[x + 1][y - 1])
                + (x == w - 1 || y == h - 1 ? 255 : gray[x + 1][y + 1]);
        return rs / 9;
    }

    /**
     * 二值化后的图像的开运算：先腐蚀再膨胀（用于去除图像的小黑点）
     *
     * @param filePath       要处理的图片路径
     * @param fileOutputPath 处理后的图片输出路径
     * @throws IOException
     */
    public static void opening(String filePath, String fileOutputPath) throws IOException {
        File file = new File(filePath);
        BufferedImage bi = ImageIO.read(file);
        // 获取当前图片的高,宽,ARGB
        int h = bi.getHeight();
        int w = bi.getWidth();
        int arr[][] = new int[w][h];
        // 获取图片每一像素点的灰度值
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                // getRGB()返回默认的RGB颜色模型(十进制)
                arr[i][j] = getImageGray(bi.getRGB(i, j));// 该点的灰度值
            }
        }

        int black = new Color(0, 0, 0).getRGB();
        int white = new Color(255, 255, 255).getRGB();
        BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
        // 临时存储腐蚀后的各个点的亮度
        int temp[][] = new int[w][h];
        // 1.先进行腐蚀操作
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                /*
                 * 为0表示改点和周围8个点都是黑，则该点腐蚀操作后为黑
                 * 由于公司图片态模糊，完全达到9个点全为黑的点太少，最后效果很差，故改为了小于30
                 * （写30的原因是，当只有一个点为白，即总共255，调用getGray方法后得到255/9 = 28）
                 */
                if (getGray(arr, i, j, w, h) < 30) {
                    temp[i][j] = 0;
                } else {
                    temp[i][j] = 255;
                }
            }
        }

        // 2.再进行膨胀操作
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                bufferedImage.setRGB(i, j, white);
            }
        }
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                // 为0表示改点和周围8个点都是黑，则该点腐蚀操作后为黑
                if (temp[i][j] == 0) {
                    bufferedImage.setRGB(i, j, black);
                    if (i > 0) {
                        bufferedImage.setRGB(i - 1, j, black);
                    }
                    if (j > 0) {
                        bufferedImage.setRGB(i, j - 1, black);
                    }
                    if (i > 0 && j > 0) {
                        bufferedImage.setRGB(i - 1, j - 1, black);
                    }
                    if (j < h - 1) {
                        bufferedImage.setRGB(i, j + 1, black);
                    }
                    if (i < w - 1) {
                        bufferedImage.setRGB(i + 1, j, black);
                    }
                    if (i < w - 1 && j > 0) {
                        bufferedImage.setRGB(i + 1, j - 1, black);
                    }
                    if (i < w - 1 && j < h - 1) {
                        bufferedImage.setRGB(i + 1, j + 1, black);
                    }
                    if (i > 0 && j < h - 1) {
                        bufferedImage.setRGB(i - 1, j + 1, black);
                    }
                }
            }
        }

        ImageIO.write(bufferedImage, "jpeg", new File(fileOutputPath));
    }

}